package ace.cmp.data.spring.data.jdbc.querydsl.rsql.core.manager.impl;

import ace.cmp.core.exception.BusinessI18nException;
import ace.cmp.data.spring.data.jdbc.querydsl.rsql.core.manager.Manager;
import ace.cmp.id.generator.api.IdGenerator;
import ace.cmp.spring.data.jdbc.querydsl.core.QuerydslRelationHelper;
import ace.cmp.spring.data.jdbc.querydsl.core.repository.QuerydslJdbcBaseRepository;
import ace.cmp.spring.data.jdbc.querydsl.rsql.core.RsqlQuerydslParser;
import ace.cmp.spring.data.jdbc.querydsl.rsql.core.RsqlQuerydslVisitor;
import ace.cmp.util.CaseFormatUtils;
import com.querydsl.core.types.Predicate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

/**
 * .
 *
 * @author caspar
 */
@Slf4j
public abstract class AbstractManager<EntityT, IdT> implements Manager<EntityT, IdT> {

  protected abstract QuerydslJdbcBaseRepository<EntityT, IdT> getRepository();

  protected abstract IdGenerator<IdT> getIdGenerator();

  protected abstract QuerydslRelationHelper getQuerydslRelationHelper();

  protected abstract RsqlQuerydslParser getQuerydslParser();

  protected abstract RsqlQuerydslVisitor getQuerydslVisitor();

  @Getter(value = AccessLevel.PROTECTED)
  @Setter(value = AccessLevel.PROTECTED)
  private Integer maxPageSize = 100;

  @Getter(value = AccessLevel.PROTECTED)
  @Setter(value = AccessLevel.PROTECTED)
  private Integer maxPage = 100;

  @Override
  public IdT getNewId() {
    return this.getIdGenerator().get();
  }

  @Transactional(rollbackFor = Exception.class)
  @Override
  public IdT save(EntityT entity) {
    return this.save(entity, true);
  }

  @Transactional(rollbackFor = Exception.class)
  @Override
  public IdT save(EntityT entity, boolean saveNullFieldFlag) {
    return this.saveEntity(entity, saveNullFieldFlag, false);
  }

  @Transactional(rollbackFor = Exception.class)
  @Override
  public IdT saveIgnore(EntityT entity, boolean saveNullFieldFlag) {
    return this.saveEntity(entity, saveNullFieldFlag, true);
  }

  @Transactional(rollbackFor = Exception.class)
  @Override
  public IdT saveEntityByConditionHasId(EntityT entity, boolean saveNullFieldFlag) {
    if (this.hasNonEntityId(entity)) {
      return this.add(entity, saveNullFieldFlag);
    } else {
      return this.modifyIgnore(entity, saveNullFieldFlag);
    }
  }

  @Transactional(rollbackFor = Exception.class)
  @Override
  public IdT add(EntityT entity) {
    return this.add(entity, true);
  }

  @Transactional(rollbackFor = Exception.class)
  @Override
  public IdT add(EntityT entity, Boolean saveNullFieldFlag) {
    return this.addEntity(entity, saveNullFieldFlag, false);
  }

  @Transactional(rollbackFor = Exception.class)
  @Override
  public IdT addIgnore(EntityT entity, boolean saveNullFieldFlag) {
    return this.addEntity(entity, saveNullFieldFlag, true);
  }

  @Transactional(rollbackFor = Exception.class)
  @Override
  public long addIgnore(Collection<EntityT> entities, boolean saveNullFieldFlag) {
    return this.addEntity(entities, saveNullFieldFlag, true, true, true, true);
  }

  @Transactional(rollbackFor = Exception.class)
  @Override
  public IdT modify(EntityT entity) {
    return this.modify(entity, true);
  }

  @Transactional(rollbackFor = Exception.class)
  @Override
  public IdT modify(EntityT entity, boolean saveNullFieldFlag) {
    return this.modifyEntity(entity, saveNullFieldFlag, false, true);
  }

  @Transactional(rollbackFor = Exception.class)
  @Override
  public IdT modifyIgnore(EntityT entity, boolean saveNullFieldFlag) {
    return this.modifyEntity(entity, saveNullFieldFlag, true, true);
  }

  @Transactional(rollbackFor = Exception.class)
  @Override
  public long modifyIgnore(Collection<EntityT> entities, boolean saveNullFieldFlag) {
    return this.modifyEntity(entities, saveNullFieldFlag, true, true, true, true);
  }

  @Transactional(rollbackFor = Exception.class)
  @Override
  public boolean remove(IdT id) {
    if (id == null) {
      return true;
    }
    this.getRepository().deleteById(id);
    return true;
  }

  @Transactional(rollbackFor = Exception.class)
  @Override
  public boolean remove(List<IdT> ids) {
    if (CollectionUtils.isEmpty(ids)) {
      return true;
    }

    this.getRepository().deleteAllById(ids);
    return true;
  }

  @Transactional(rollbackFor = Exception.class)
  @Override
  public boolean remove(Predicate predicate) {
    Assert.notNull(predicate, "predicate must not be null");
    this.getRepository().deleteWhere(predicate);
    return true;
  }

  @Transactional(rollbackFor = Exception.class)
  public boolean removeLogic(IdT id) {
    return this.removeLogic(Arrays.asList(id));
  }

  @Transactional(rollbackFor = Exception.class)
  @Override
  public boolean removeLogic(List<IdT> ids) {
    if (CollectionUtils.isEmpty(ids)) {
      return true;
    }
    this.getRepository().deleteLogicById(ids);
    return true;
  }

  @Transactional(rollbackFor = Exception.class)
  @Override
  public boolean removeLogic(Predicate predicate) {
    Assert.notNull(predicate, "predicate must not be null");
    this.getRepository().deleteLogicBy(predicate);
    return true;
  }

  @Override
  public EntityT getById(IdT id) {
    EntityT entity = this.getEntityById(id);
    return entity;
  }

  @Override
  public List<EntityT> getListByIdIn(List<IdT> ids) {
    List<EntityT> entities = this.getEntityListByIdIn(ids);
    return entities;
  }

  @Override
  public Map<IdT, EntityT> getMapByIdIn(List<IdT> ids) {
    Map<IdT, EntityT> entityMap = this.getEntityMapByIdIn(ids);
    return entityMap;
  }

  @Override
  public Page<EntityT> search(String query, Pageable pageable) {
    Page<EntityT> entityPage = this.searchEntity(query, pageable);
    return entityPage;
  }

  @Override
  public Page<EntityT> search(Predicate predicate, Pageable pageable) {
    Page<EntityT> entityPage = this.searchEntity(predicate, pageable);
    return entityPage;
  }

  @Override
  public Predicate parseRsql(String rsql) {
    Predicate predicate = null;
    if (StringUtils.isNotEmpty(rsql)) {
      try {
        predicate = this.getQuerydslParser().parse(rsql, this.getQuerydslVisitor());
      } catch (Exception ex) {
        log.warn("query查询语句解析失败", ex);
        this.assertIsTrue(
            true, this.getI18nCodePrefix() + "." + "search-query-parse-error",
            "query查询语句解析失败");
      }
    }
    return predicate;
  }

  @Override
  public EntityT getOne(Predicate predicate) {
    return this.getRepository().findOne(predicate).orElse(null);
  }

  @Override
  public List<EntityT> getList(Predicate predicate) {
    return this.getRepository().findAll(predicate);
  }

  @Override
  public Map<IdT, EntityT> getMap(Predicate predicate) {
    return this.getRepository().findAllMapBy(predicate);
  }

  @Override
  public boolean isNew(EntityT entity) {
    return !this.hasEntityId(entity) || this.existNonById(entity);
  }

  @Override
  public IdT getEntityId(EntityT entity) {
    return this.getQuerydslRelationHelper().getEntityId(entity);
  }

  @Override
  public boolean hasEntityId(EntityT entity) {
    return this.getEntityId(entity) != null;
  }

  @Override
  public boolean hasNonEntityId(EntityT entity) {
    return !this.hasEntityId(entity);
  }

  @Override
  public boolean existById(EntityT entity) {
    IdT id = this.getEntityId(entity);
    return this.getRepository().existsById(id);
  }

  @Override
  public boolean existNonById(EntityT entity) {
    return !this.existById(entity);
  }


  protected Page<EntityT> searchEntity(String rsql, Pageable pageable) {

    Predicate predicate = this.parseRsql(rsql);

    return this.searchEntity(predicate, pageable);
  }

  protected Page<EntityT> searchEntity(Predicate predicate, Pageable pageable) {

    this.assertIsTrue(
        pageable.getPageSize() > this.maxPageSize,
        this.getI18nCodePrefix() + "." + "max-page-size-limit",
        new Object[]{this.maxPageSize},
        "每页显示最大数量{0}");
    this.assertIsTrue(
        pageable.getPageNumber() > this.maxPage,
        this.getI18nCodePrefix() + "." + "max-page-limit",
        new Object[]{this.maxPage},
        "最多显示{0}页数据,请增加过滤条件,缩小数据范围");

    Page<EntityT> pageResult = null;
    if (predicate != null) {
      pageResult = this.getRepository().findAll(predicate, pageable);
    } else {
      pageResult = this.getRepository().findAll(pageable);
    }
    return pageResult;
  }

  protected EntityT getEntityById(IdT id) {
    return this.getRepository().getById(id);
  }

  protected List<EntityT> getEntityListByIdIn(List<IdT> ids) {
    if (CollectionUtils.isEmpty(ids)) {
      return new ArrayList<>();
    }
    return this.getRepository().getByIdIn(ids);
  }

  protected Map<IdT, EntityT> getEntityMapByIdIn(List<IdT> ids) {
    return this.getRepository().findAllMapByIds(ids);
  }

  protected IdT saveEntity(
      EntityT entity, boolean saveNullFieldFlag, boolean enableFrameworkForce) {
    if (this.isNew(entity)) {
      return this.addEntity(entity, saveNullFieldFlag, enableFrameworkForce);
    } else {
      return this.modifyEntity(entity, saveNullFieldFlag, enableFrameworkForce, false);
    }
  }

  protected IdT addEntity(EntityT entity, boolean saveNullFieldFlag, boolean enableFrameworkForce) {
    return this.addEntity(
        entity,
        saveNullFieldFlag,
        enableFrameworkForce,
        enableFrameworkForce,
        enableFrameworkForce,
        enableFrameworkForce);
  }

  protected IdT addEntity(
      EntityT entity,
      boolean saveNullFieldFlag,
      boolean enableVersionForce,
      boolean enableCreatedDateForce,
      boolean enableLastModifiedDateForce,
      boolean enableLogicDeleteForce) {
    if (this.hasNonEntityId(entity)) {
      IdT entityId = this.getIdGenerator().get();
      this.setEntityId(entity, entityId);
    }

    this.getRepository()
        .insert(
            entity,
            saveNullFieldFlag,
            enableVersionForce,
            enableCreatedDateForce,
            enableLastModifiedDateForce,
            enableLogicDeleteForce);

    return this.getEntityId(entity);
  }

  protected long addEntity(
      Collection<EntityT> entities,
      boolean saveNullFieldFlag,
      boolean enableVersionForce,
      boolean enableCreatedDateForce,
      boolean enableLastModifiedDateForce,
      boolean enableLogicDeleteForce) {

    entities.forEach(entity -> {
      if (this.hasNonEntityId(entity)) {
        IdT entityId = this.getIdGenerator().get();
        this.setEntityId(entity, entityId);
      }
    });

    return this.getRepository()
        .insert(
            entities,
            saveNullFieldFlag,
            enableVersionForce,
            enableCreatedDateForce,
            enableLastModifiedDateForce,
            enableLogicDeleteForce);
  }

  protected IdT modifyEntity(
      EntityT entity,
      boolean saveNullFieldFlag,
      boolean enableFrameworkForce,
      boolean enableCheckExistOldData) {
    return this.modifyEntity(
        entity,
        saveNullFieldFlag,
        enableFrameworkForce,
        enableFrameworkForce,
        enableFrameworkForce,
        enableFrameworkForce,
        enableCheckExistOldData);
  }

  protected IdT modifyEntity(
      EntityT entity,
      boolean saveNullFieldFlag,
      boolean enableVersionOnlyUpdate,
      boolean enableCreatedDateForce,
      boolean enableLastModifiedDateForce,
      boolean enableLogicDeleteForce,
      boolean enableCheckExistOldData) {
    if (enableCheckExistOldData) {
      assertIsTrue(
          this.existNonById(entity),
          this.getI18nCodePrefix() + "." + "error-modify-entity-not-exist",
          "数据不存在");
    }

    long resultRow = this.getRepository()
        .updateById(
            entity,
            saveNullFieldFlag,
            enableVersionOnlyUpdate,
            enableCreatedDateForce,
            enableLastModifiedDateForce,
            enableLogicDeleteForce);

    this.assertIsTrue(resultRow <= 0, this.getI18nCodePrefix() + "." + "modify-fail", "修改失败");

    return this.getEntityId(entity);
  }

  protected long modifyEntity(
      Collection<EntityT> entities,
      boolean saveNullFieldFlag,
      boolean enableVersionOnlyUpdate,
      boolean enableCreatedDateForce,
      boolean enableLastModifiedDateForce,
      boolean enableLogicDeleteForce) {

    return this.getRepository()
        .updateById(
            entities,
            saveNullFieldFlag,
            enableVersionOnlyUpdate,
            enableCreatedDateForce,
            enableLastModifiedDateForce,
            enableLogicDeleteForce);
  }

  protected void setEntityId(EntityT entity, IdT entityId) {
    this.getQuerydslRelationHelper().setEntityId(entity, entityId);
  }

  protected void assertIsTrue(boolean value, String code, Object[] args, String defaultMessage) {
    if (value) {
      throw new BusinessI18nException(code, args, defaultMessage);
    }
  }

  protected void assertIsTrue(boolean value, String code, String defaultMessage) {
    this.assertIsTrue(value, code, null, defaultMessage);
  }

  protected String getI18nCodePrefix() {
    return CaseFormatUtils.convertClassFullNameUpperCamelToLowerUnderscore(this.getClass());
  }

}
